From: Robert Lipe Date: Thu, 29 Dec 2016 05:50:01 +0000 (-0500) Subject: Add preliminary write support for GeoJSON. X-Git-Tag: archive/raspbian/1.10.0+ds-2+rpi1~1^2~12^2~9^2~9^2~6 X-Git-Url: https://dgit.raspbian.org/%22http://www.example.com/cgi/%22/%22http:/www.example.com/cgi/%22?a=commitdiff_plain;h=ecbf04080c7f5107ffe29dc6c40c80046cb732c8;p=gpsbabel.git Add preliminary write support for GeoJSON. --- diff --git a/Makefile.in b/Makefile.in index c7a24b66f..83af60484 100644 --- a/Makefile.in +++ b/Makefile.in @@ -81,7 +81,7 @@ ALL_FMTS=$(MINIMAL_FMTS) gtm.o gpsutil.o \ vpl.o teletype.o jogmap.o bushnell.o bushnell_trl.o wintec_tes.o \ subrip.o garmin_xt.o garmin_fit.o lowranceusr4.o \ mtk_locus.o googledir.o mapbar_track.o f90g_track.o mapfactor.o energympro.o \ - mynav.o ggv_bin.o globalsat_sport.o + mynav.o ggv_bin.o globalsat_sport.o geojson.o FMTS=@FMTS@ diff --git a/geojson.cc b/geojson.cc new file mode 100644 index 000000000..4ddc233e1 --- /dev/null +++ b/geojson.cc @@ -0,0 +1,169 @@ +/* + Copyright (C) 2016 Robert Lipe, robertlipe+source@gpsbabel.org + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA + + */ +#include "defs.h" +#include +#include +#include +#include "src/core/file.h" + +static gbfile* ofd; +static const char MYNAME[] = "geojson"; +static char* compact_opt = NULL; +static QJsonObject* track_object = NULL; +static QJsonArray* track_coords = NULL; + +static arglist_t geojson_args[] = { + {"compact", &compact_opt, "Compact Output. Default is off.", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX } , + ARG_TERMINATOR +}; + +static void +geojson_rd_init(const QString& fname) { +} + +QJsonArray* feature_collection = nullptr; + +static void +geojson_wr_init(const QString& fname) { + feature_collection = new QJsonArray; + ofd = gbfopen(fname, "w", MYNAME); +} + +static void +geojson_waypt_pr(const Waypoint* waypoint) { + QJsonObject object; + object["type"] = "Feature"; + + QJsonObject geometry; + geometry["type"] = "Point"; + + QJsonArray coords; + coords.append(waypoint->longitude); + coords.append(waypoint->latitude); + if (waypoint->altitude != unknown_alt && waypoint->altitude != 0) { + coords.append(waypoint->altitude); + } + + geometry["type"] = "Point"; + geometry["coordinates"] = coords; + object["geometry"] = geometry; + + // Build up the properties. + QJsonObject properties; + if (!waypoint->shortname.isEmpty()) { + properties["name"] = waypoint->shortname; + } + if (!waypoint->description.isEmpty()) { + properties["description"] = waypoint->description; + } + UrlLink link = waypoint->GetUrlLink(); + if (!link.url_.isEmpty()) { + properties["url"] = link.url_; + } + if (!link.url_link_text_.isEmpty()) { + properties["urlname"] = link.url_link_text_; + } + if (!properties.empty()) { + object["properties"] = properties; + } + + feature_collection->append(object); +} + +static void +geojson_rd_deinit() { +} + +static void +geojson_wr_deinit(void) { + QJsonObject object; + object["type"] = "FeatureCollection"; + object["features"] = *feature_collection; + + QJsonDocument save(object); + QJsonDocument::JsonFormat style; + style = compact_opt ? QJsonDocument::Compact : QJsonDocument::Indented; + gbfputs(save.toJson(style),ofd); + + gbfclose(ofd); + ofd = NULL; + delete feature_collection; + feature_collection = nullptr; +} + +static void +geojson_read(void) { +} + +static void geojson_track_hdr(const route_head* track) { + track_object = new QJsonObject(); + + (*track_object)["type"] = "Feature"; + track_coords = new QJsonArray(); + + QJsonObject properties; + if (!track->rte_name.isEmpty()) { + properties["name"] = track->rte_name; + } + (*track_object)["properties"] = properties; +} + +static void geojson_track_disp(const Waypoint* trackpoint) { + + QJsonArray coords; + coords.append(trackpoint->longitude); + coords.append(trackpoint->latitude); + if (trackpoint->altitude != unknown_alt && trackpoint->altitude != 0) { + coords.append(trackpoint->altitude); + } + (*track_coords).append(coords); +} + +static void geojson_track_tlr(const route_head* track) { + QJsonObject geometry; + geometry["type"] = "LineString"; + geometry["coordinates"] = *track_coords; + (*track_object)["geometry"] = geometry; + feature_collection->append(*track_object); + delete track_object; + track_object = NULL; + delete track_coords; + track_coords = NULL; +} + +static void +geojson_write(void) { + waypt_disp_all(geojson_waypt_pr); + track_disp_all(geojson_track_hdr, geojson_track_tlr, geojson_track_disp); +} + +ff_vecs_t geojson_vecs = { + ff_type_file, + { (ff_cap)(/*ff_cap_read | */ff_cap_write), ff_cap_write, ff_cap_none }, + geojson_rd_init, + geojson_wr_init, + geojson_rd_deinit, + geojson_wr_deinit, + geojson_read, + geojson_write, + NULL, + geojson_args, + CET_CHARSET_UTF8, 0 /* CET-REVIEW */ +}; diff --git a/reference/geocaching~json.json b/reference/geocaching~json.json new file mode 100644 index 000000000..f82db551b --- /dev/null +++ b/reference/geocaching~json.json @@ -0,0 +1,149 @@ +{ + "features": [ + { + "geometry": { + "coordinates": [ + -87.134699999999995, + 35.972033332999999 + ], + "type": "Point" + }, + "properties": { + "description": "Mountain Bike Heaven by susy1313", + "name": "GCEBB", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=3771", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.679550000000006, + 36.090683333000001 + ], + "type": "Point" + }, + "properties": { + "description": "The Troll by a182pilot & Family", + "name": "GC1A37", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=6711", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.620116667000005, + 35.996266667 + ], + "type": "Point" + }, + "properties": { + "description": "Dive Bomber by JoGPS & family", + "name": "GC1C2B", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=7211", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.648616666999999, + 36.038483333000002 + ], + "type": "Point" + }, + "properties": { + "description": "FOSTER by JoGPS & Family", + "name": "GC25A9", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=9641", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.741766666999993, + 36.112183332999997 + ], + "type": "Point" + }, + "properties": { + "description": "Logan Lighthouse by JoGps & Family", + "name": "GC2723", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=10019", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.790516667000006, + 36.064083332999999 + ], + "type": "Point" + }, + "properties": { + "description": "Ganier Cache by Susy1313", + "name": "GC2B71", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=11121", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.809733332999997, + 36.087766666999997 + ], + "type": "Point" + }, + "properties": { + "description": "Shy's Hill by FireFighterEng33", + "name": "GC309F", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=12447", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.891999999999996, + 36.057499999999997 + ], + "type": "Point" + }, + "properties": { + "description": "GittyUp by JoGPS / Warner Parks", + "name": "GC317A", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=12666", + "urlname": "Cache Details" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + -86.867283333000003, + 36.082799999999999 + ], + "type": "Point" + }, + "properties": { + "description": "Inlighting by JoGPS / Warner Parks", + "name": "GC317D", + "url": "http://www.geocaching.com/seek/cache_details.asp?ID=12669", + "urlname": "Cache Details" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" +} diff --git a/reference/track/segmented_tracks~geojson.json b/reference/track/segmented_tracks~geojson.json new file mode 100644 index 000000000..0dcedc8c7 --- /dev/null +++ b/reference/track/segmented_tracks~geojson.json @@ -0,0 +1,211 @@ +{ + "features": [ + { + "geometry": { + "coordinates": [ + [ + -86.844139609999999, + 35.826145640999997 + ], + [ + -86.843587434, + 35.824858661 + ], + [ + -86.843868790000002, + 35.825508290000002 + ], + [ + -86.843399026, + 35.825659280000004 + ], + [ + -86.843647121000004, + 35.826200632000003 + ], + [ + -86.843137330000005, + 35.825023495000003 + ], + [ + -86.842846089999995, + 35.825764722999999 + ], + [ + -86.842592054999997, + 35.825212972999999 + ], + [ + -86.842083658999996, + 35.825409925000002 + ], + [ + -86.842527868000005, + 35.826530337999998 + ], + [ + -86.841916886999996, + 35.826227992 + ], + [ + -86.841882385999995, + 35.826752995 + ], + [ + -86.841275397999993, + 35.825879004999997 + ], + [ + -86.840954045999993, + 35.826506488 + ], + [ + -86.840764704999998, + 35.826053416000001 + ], + [ + -86.840254282000004, + 35.826187447999999 + ], + [ + -86.840499359999995, + 35.826664997000002 + ], + [ + -86.840954045999993, + 35.826506488 + ], + [ + -86.839682814, + 35.826441473000003 + ], + [ + -86.840327092999999, + 35.827349034999997 + ], + [ + -86.839510243000007, + 35.827133899000003 + ], + [ + -86.839451225000005, + 35.827576071000003 + ], + [ + -86.838781982, + 35.826743954000001 + ] + ], + "type": "LineString" + }, + "properties": { + "name": "No Times" + }, + "type": "Feature" + }, + { + "geometry": { + "coordinates": [ + [ + -86.844139609999999, + 35.836145641000002 + ], + [ + -86.843587434, + 35.834858660999998 + ], + [ + -86.843868790000002, + 35.83550829 + ], + [ + -86.843399026, + 35.835659280000002 + ], + [ + -86.843647121000004, + 35.836200632000001 + ], + [ + -86.843137330000005, + 35.835023495000002 + ], + [ + -86.842846089999995, + 35.835764722999997 + ], + [ + -86.842592054999997, + 35.835212972999997 + ], + [ + -86.842083658999996, + 35.835409925 + ], + [ + -86.842527868000005, + 35.836530338000003 + ], + [ + -86.841916886999996, + 35.836227991999998 + ], + [ + -86.841882385999995, + 35.836752994999998 + ], + [ + -86.841275397999993, + 35.835879005000002 + ], + [ + -86.840954045999993, + 35.836506487999998 + ], + [ + -86.840764704999998, + 35.836053415999999 + ], + [ + -86.840254282000004, + 35.836187447999997 + ], + [ + -86.840499359999995, + 35.836664997 + ], + [ + -86.840954045999993, + 35.836506487999998 + ], + [ + -86.839682814, + 35.836441473000001 + ], + [ + -86.840327092999999, + 35.837349035000003 + ], + [ + -86.839510243000007, + 35.837133899000001 + ], + [ + -86.839451225000005, + 35.837576071000001 + ], + [ + -86.838781982, + 35.836743953999999 + ] + ], + "type": "LineString" + }, + "properties": { + "name": "With Times" + }, + "type": "Feature" + } + ], + "type": "FeatureCollection" +} diff --git a/testo.d/geojson.test b/testo.d/geojson.test new file mode 100644 index 000000000..c4191efd5 --- /dev/null +++ b/testo.d/geojson.test @@ -0,0 +1,10 @@ +# +# As this format is initially write-only, we're comparing our own "golden" +# output to what the newly built writer creates. +# + +gpsbabel -i gpx -f ${REFERENCE}/geocaching.gpx -o geojson -F ${TMPDIR}/geo.json +compare ${REFERENCE}/geocaching~json.json ${TMPDIR}/geo.json + +gpsbabel -i gpx -f ${REFERENCE}/track/segmented_tracks.gpx -o geojson -F ${TMPDIR}/track.json +compare ${REFERENCE}/track/segmented_tracks~geojson.json ${TMPDIR}/track.json diff --git a/vecs.cc b/vecs.cc index 7fde64b39..1b739245c 100644 --- a/vecs.cc +++ b/vecs.cc @@ -54,6 +54,7 @@ extern ff_vecs_t gcdb_vecs; extern ff_vecs_t gdb_vecs; extern ff_vecs_t geoniche_vecs; extern ff_vecs_t geo_vecs; +extern ff_vecs_t geojson_vecs; extern ff_vecs_t globalsat_sport_vecs; extern ff_vecs_t glogbook_vecs; extern ff_vecs_t google_dir_vecs; @@ -1077,6 +1078,13 @@ vecs_t vec_list[] = { "trc", NULL, }, + { + &geojson_vecs, + "geojson", + "GeoJson", + "json", + NULL, + }, { &ggv_bin_vecs, "ggv_bin", diff --git a/xmldoc/formats/geojson.xml b/xmldoc/formats/geojson.xml new file mode 100644 index 000000000..ae1eb44b1 --- /dev/null +++ b/xmldoc/formats/geojson.xml @@ -0,0 +1,31 @@ + +This module supports a subset of the GeoJSON format. + + +GeoJSON is a poor fit for GPSBabel's internal data structures as GPSBabel +was designed more around common GPS features (waypoints, tracks, routes) +than about GIS-style concepts like MultiPolygons or Geometry Collections. +In reality, for all but the most simple uses (such as converting a format +that GPSBabel supports well to something like Leaflet, you should not expect +high fidelity transfers through this format. + + +Initially, only write support for waypoints and tracks is available. +Waypoints are converted to a FeatureCollection of Points. +The properties for name and description are written, where available. +Tracks are converted to a LineString. + + +Potential future work includes the ability to read Point Features and/or +LineStrings as these would map into our concept of waypoints and +routes/tracks. +The potentially nested/recursive nature of GeoJSON in general would be +an awkward implementation. + + +Initial development was free-handed by looking at the GeoJSON RFC. Corner cases were handled by +using GDAL's ogr2ogr +to convert GPX to JSON and compare the output. The results were then + JSON validated and viewed on + JSON web viewer. + diff --git a/xmldoc/formats/options/geojson-compact.xml b/xmldoc/formats/options/geojson-compact.xml new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/xmldoc/formats/options/geojson-compact.xml @@ -0,0 +1 @@ +